#include "dag.h"
#include "dag_types.h"
#include "dag_frame_op_factory.h"
#include "dag_operators.h"

#include <fstream>
#include <string>
#include <string.h>

using namespace dag;
using namespace std;

FrameOperator::FrameOperator(const std::string& opName) 
:	m_Name(opName)
{	
}

FrameOperator::~FrameOperator() throw() {
}

void FrameOperator::serialize(const string& filename) const {
	ofstream of;
	of.exceptions(ios::badbit);
	
	try {
		of.open(filename.c_str(), ios::out | ios::binary | ios::trunc);
		
		/* write file header */
		of.write(dag_file_header, strlen(dag_file_header));
		
		/* write frame operator */
		serialize(of);
	}
	catch (FileWriteError& err) {
		throw FileWriteError(filename);
	}
	catch (fstream::failure& err) {
		throw FileWriteError(filename);
	}
}

void FrameOperator::serialize(ostream& binaryStream) const {
	binaryStream.write(dag_operator_file_token, 
					   strlen(dag_operator_file_token));
	
	/* write size of class name */
	dag_uint32_t idSize = _frameOperatorID().size();
	if (idSize == 0) {
		throw InvalidFrameOpId();
	}
	
	/* write size of frame operator id */
	binaryStream.write((const char*)&idSize, dag_uint32_size);
	
	/* write frame operator id */
	binaryStream.write(_frameOperatorID().c_str(), idSize);
	
	/* write frame operator */
	_serialize(binaryStream);
}

auto_ptr<FrameOperator> FrameOperator::inflate(const string& filename) {
	
	register_internal_inflators();
	
	ifstream binaryStream;
	auto_ptr<FrameOperator> frameOp;

	char* fileHeader = NULL;
	binaryStream.exceptions(ios::badbit);
	
	try {
		binaryStream.open(filename.c_str(), ios::in | ios::binary);
		
		size_t fileHeaderLen = strlen(dag_file_header) + 1;
		/* init string buffer */
		fileHeader = new char[fileHeaderLen];
		memset(fileHeader, 0, fileHeaderLen);
		
		/* check file header */
		binaryStream.read(fileHeader, fileHeaderLen - 1);
		if (strcmp(fileHeader, dag_file_header) != 0) {
			throw InvalidToken(dag_file_header, fileHeader);
		}
		
		frameOp = FrameOperator::inflate(binaryStream);
		
		/* cleanup resources */
		delete[] fileHeader;
		fileHeader = NULL;
	}
	catch (fstream::failure& err) {
		delete[] fileHeader;
		fileHeader = NULL;
		throw FileReadError(filename);
	}
	catch (...) {
		delete[] fileHeader;
		fileHeader = NULL;
		throw;
	}
	
	return frameOp;
}

auto_ptr<FrameOperator> FrameOperator::inflate(istream& binaryStream) {
	char* frameOpHeader = NULL;
	char* frameOpId = NULL;
	auto_ptr<FrameOperator> frameOp(NULL);
	
	try {
		size_t frameOpHeaderLen = strlen(dag_operator_file_token) + 1;
		frameOpHeader = new char[frameOpHeaderLen];
		memset(frameOpHeader, 0, frameOpHeaderLen);
		
		/* check frame operator header */
		binaryStream.read(frameOpHeader, frameOpHeaderLen - 1);
		if (strcmp(frameOpHeader, dag_operator_file_token) != 0) {
			throw InvalidToken(frameOpHeader, dag_operator_file_token);
		}
		
		/* get operator ID */
		dag_uint32_t opIdSize = 0;
		binaryStream.read((char*)&opIdSize, dag_uint32_size);
		if (opIdSize == 0) {
			throw InvalidFrameOpId();
		}
		
		frameOpId = new char[opIdSize + 1];
		memset(frameOpId, 0, opIdSize + 1);
		binaryStream.read(frameOpId, opIdSize);
		
		/* creat frame operator using factory */
		frameOp = FrameOpFactory::instance().inflate(frameOpId, 
													 binaryStream);

		/* TODO: !!!! incorporate name writing and reading,
		 * and some sort of dag_io header */
		
		/* cleanup resources */
		delete[] frameOpId;
		frameOpId = NULL;
		delete[] frameOpHeader;
		frameOpHeader = NULL;
	}
	catch (...) {
		/* cleanup resources */
		delete[] frameOpId;
		frameOpId = NULL;
		delete[] frameOpHeader;
		frameOpHeader = NULL;
		throw;
	}
	
	return frameOp;
}

const string& FrameOperator::name() const {
	return m_Name;
}

void FrameOperator::name(const string& opName) {
	m_Name = opName;
}